/**************************************************************************************************
* Title: SimulationManager.cpp
* Authors: Gael Huber, Erin Mooney
* Description: Manages the actual game
**************************************************************************************************/
#include "SimulationManager.h"

// Singleton instance
template<> SimulationManager* Singleton<SimulationManager>::singleton = 0;

// Constructor
SimulationManager::SimulationManager(void) : numEntities(0), numBoxes(0) {
}	

// Destructor
SimulationManager::~SimulationManager(void) {
}

// Initialize
void SimulationManager::startUp(void) {
	// random seed
	srand((unsigned int) time(0));

	boxes = std::vector<Entity*>();

	// create the input manager
	inputManager = new InputManager();
	
	// create the render manager
	renderManager = new RenderManager();
	renderManager->startUp();

	// create physics manager
	physicsManager = new PhysicsManager();
	physicsManager->startUp();

	// Initialize the matrix reader
	matrixReader = new MatrixReader();

	// Create and generate the terrain
	createTerrain(Vector3(0.0f, 0.0f, 0.0f));

	// Create the player
	Mesh* mesh = PrimitiveFactory::createSphere(0.02f, 5);//Rectangle(0.01f, 0.01f, 0.01f);//createSphere(0.02f, 5);
	glm::vec3 v = *renderManager->getCamera()->getPosition();
	v *= -1.0f;
	Sphere* body = new Sphere(Vector3(v.x, v.y - 0.03f, v.z), 0.02f);
	body->mass = 1.0f;
	player = new Player(body, mesh);
	entityList.push_back(player);

	//renderManager->getSingletonPtr()->getCamera()->rotate(180);

	// Initialize the timer
	physicsManager->initializeTimer();
}

// Shutdown
void SimulationManager::shutDown(void) {
}

/**************************************************************************************************
* Get the singleton instance
**************************************************************************************************/
SimulationManager& SimulationManager::getSingleton(void) { 
	assert(singleton);
	return *singleton;
}

/**************************************************************************************************
* Get the singleton pointer
**************************************************************************************************/
SimulationManager* SimulationManager::getSingletonPtr(void) {
	return singleton;
}

// Update
void SimulationManager::gameLoop(void) {
	MSG msg;	// Windows message
	BOOL done = FALSE;	// Bool variable to exit loop

	while( !done ) {
		// Is there a message waiting?
		if( PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) ) {
			// Have we received a quit message?
			if( msg.message == WM_QUIT ) {
				done = TRUE;	// If so, we want to quit
			} else {
				// Otherwise deal with Windows messages
				TranslateMessage(&msg);	// Translate the message
				DispatchMessage(&msg);	// Dispatch the message
			}
		} else { // If there is no message
			// handle input
			inputManager->handleInput();

			// Run if the game is not paused
			if(!getGamePaused()) {
				// Update physics
				physicsManager->update();

				// Post-physics update
				// Define start and end of vector
				std::vector<Entity*>::iterator begin = entityList.begin();
				std::vector<Entity*>::iterator end = entityList.end();

				// Iterate through the list and update each object
				for(std::vector<Entity*>::iterator itr = begin; itr != end; ++itr) {
					// Apply update
					(*itr)->postPhysicsUpdate();
				}
			}

			player->update();

			// Draw the scene
			renderManager->renderScene();
		}
	}
}

/**************************************************************************************************
* Creates the terrain
**************************************************************************************************/
void SimulationManager::createTerrain(Vector3 center) {
	// Create and generate the terrain
	SmurfsPolygon::maxHeight = -FLT_MAX;
	SmurfsPolygon::minHeight = FLT_MAX;
	SmurfsPolygon::maxSlope = -FLT_MAX;

	terrain = new Terrain();
	terrain->generateTerrain(center);
	terrain->getMesh()->screenCoords = center;
	renderManager->addToScene(terrain->getMesh());

	// pass terrain (array of polygons) into physics manager to build the tree
	Vector3 treeSize = Vector3(terrain->getWidth(), terrain->getHeight(), terrain->getDepth());
	physicsManager->buildTerrainTree(terrain->getPolygons(), terrain->getPolyWidth(), 
		terrain->getPolyHeight(), terrain->getMesh()->screenCoords + Vector3(0, 
		terrain->getMaxHeight() - (treeSize.y / 2.0f), 0),	treeSize);
}

/**************************************************************************************************
* Regenerate the player and the level
**************************************************************************************************/
void SimulationManager::regenPlayer(void) {
	// remove all of the objects from the world
	while(numBoxes > 0)
		removeBox();

	// Delete the terrain from the list of meshes
	renderManager->removeFromScene(terrain->getMesh());

	// Delete the terrain from physics
	physicsManager->destroyTerrainTree();

	// Re-create the terrain and re-add it to physics
	createTerrain(Vector3(player->getBody()->center.x, player->getBody()->center.y - 30.0f, player->getBody()->center.z));

	physicsManager->initializeTimer();
}

// getter for the number of entities in the world
int SimulationManager::getNumEntities(void) { return numEntities; }
Player* SimulationManager::getPlayer(void) { return player; }

/**************************************************************************************************
* returns the number of boxes in the world
**************************************************************************************************/
int SimulationManager::getNumBoxes(void) { return numBoxes; }
	
/**************************************************************************************************
* creates and adds a box to the world
**************************************************************************************************/
void SimulationManager::addBox(void) {
	// create entity
	Entity* entity;

	// create rigid body
	Sphere* body = new Sphere(Vector3(), 0.05f);//
	body->mass = 1.0f;
	body->halfWidths = Vector3(0.05f, 0.05f, 0.05f);
	body->init();
	body->center = Vector3(player->getBody()->center.x, player->getBody()->center.y + 1.0f, player->getBody()->center.z);
	
	// Generate a random force
	Vector3 force = Vector3();
	for(int i = 0; i < 3; i++) {
		force[i] = (((float)rand() / RAND_MAX) * 2.0f - 1.0f);
	}
	force.y = abs(force.y / 3.0f);
	force = force.getNormalized() * 1000.0f;
	body->appliedForce = force;

	// create the mesh
	Mesh* mesh = PrimitiveFactory::createRectangle(0.25f, 0.25f, 0.25f);
	mesh->screenCoords = body->center;

	entity = new Entity(mesh, body);
	RenderManager::getSingletonPtr()->addToScene(mesh);
	PhysicsManager::getSingletonPtr()->addToWorld(body);
	
	// check if the list of boxes has been exceeded and remove if needed
	if(numBoxes >= MAX_BOXES)
		removeBox();

	// add to the list of boxes
	boxes.push_back(entity);

	// add to the entity list
	entityList.push_back(entity);

	numBoxes++;
}

/**************************************************************************************************
* removes the first box in the list from both lists
**************************************************************************************************/
void SimulationManager::removeBox(void) {
	// get the id of the first element
	UINT64 boxID;
	boxID = boxes.front()->getID();

	// remove from the list of boxes
	boxes.erase(boxes.begin());

	// remove from the list of entities
	std::vector<Entity*>::iterator begin = entityList.begin();
	std::vector<Entity*>::iterator end = entityList.end();

	// Iterate through the list and update each object
	for(std::vector<Entity*>::iterator itr = begin; itr != end; ++itr) {
		// check the ID
		if((*itr)->getID() == boxID) {
			(*itr)->removeFromWorld();
			entityList.erase(itr);
			break;
		}
	}

	numBoxes--;
}